#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
// #include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define KEY_CNT 1      // 设备数
#define KEY_NAME "key" // 设备名

#define KEY_VALUE 0xF0 // 按键值
#define INVA_KEY 0x00  // 按键无效值

struct key_dev
{
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
    int key_gpio;
    atomic_t key_value; // 按键值
};

static struct key_dev keyDev; // 设备结构体

static int keyIO_init(void)
{
    int ret = 0;
    const char *str;

    keyDev.nd = of_find_node_by_path("/key");
    if (keyDev.nd == NULL)
    {
        printk("key node can not find!\r\n");
        return -EINVAL;
    }
    ret = of_property_read_string(keyDev.nd, "status", &str);
    if (ret < 0)
    {
        printk("key status read failed!\r\n");
        return -EINVAL;
    }

    if (strcmp(str, "okay"))
    {
        printk("key status is not okay!\r\n");
        return -EINVAL;
    }

    ret = of_property_read_string(keyDev.nd, "compatible", &str);
    if (ret < 0)
    {
        printk("key compatible read failed!\r\n");
        return -EINVAL;
    }
    if (strcmp(str, "alientek,key"))
    {
        printk("key compatible is not alientek,key!\r\n");
        return -EINVAL;
    }

    keyDev.key_gpio = of_get_named_gpio(keyDev.nd, "key-gpio", 0);
    if (keyDev.key_gpio < 0)
    {
        printk("key gpio read failed!\r\n");
        return -EINVAL;
    }
    printk("key gpio num = %d\r\n", keyDev.key_gpio);

    ret = gpio_request(keyDev.key_gpio, "key-gpio");
    if (ret < 0)
    {
        printk(KERN_ERR "key gpio request failed!\r\n");
        return ret;
    }
    // 设置gpio为输入模式
    ret = gpio_direction_input(keyDev.key_gpio);
    if (ret < 0)
    {
        printk(KERN_ERR "key gpio set input mode failed!\r\n");
        return ret;
    }
    return 0;
}

static int key_open(struct inode *inode, struct file *filp)
{
    int ret = 0;
    filp->private_data = &keyDev;

    ret = keyIO_init();
    if (ret < 0)
    {
        printk("keyIO_init failed!\r\n");
        return -EINVAL;
    }
    return 0;
}

static ssize_t key_read(struct file *filp, char __user *buf, size_t count, loff_t *off)
{
    int ret = 0;
    int value = 0;
    struct key_dev *dev = filp->private_data;

    if (gpio_get_value(dev->key_gpio) == 1)
    {
        while (gpio_get_value(dev->key_gpio))
            ;
        atomic_set(&dev->key_value, KEY_VALUE);
    }
    else
    {
        atomic_set(&dev->key_value, INVA_KEY);
    }

    value = atomic_read(&dev->key_value);
    ret = copy_to_user(buf, &value, sizeof(value));
    return ret;
}

static int key_release(struct inode *inode, struct file *filp)
{
    struct key_dev *dev = filp->private_data;
    gpio_free(dev->key_gpio);
    return 0;
}

static struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .open = key_open,
    .read = key_read,
    .write = NULL,
    .release = key_release,
};

static int __init mykey_init(void)
{
    int ret = 0;
    // 初始化原子变量
    keyDev.key_value = (atomic_t)ATOMIC_INIT(0);

    atomic_set(&keyDev.key_value, INVA_KEY);

    if (keyDev.major)
    {
        keyDev.devid = MKDEV(keyDev.major, 0);
        ret = register_chrdev_region(keyDev.devid, KEY_CNT, KEY_NAME);
        if (ret < 0)
        {
            pr_err("cannot register %s char driver [ret=%d]\n",
                   KEY_NAME, KEY_CNT);
            return -EIO;
        }
    }
    else
    {
        ret = alloc_chrdev_region(&keyDev.devid, 0, KEY_CNT, KEY_NAME);
        if (ret < 0)
        {
            pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n",
                   KEY_NAME, ret);
            return -EIO;
        }
        keyDev.major = MAJOR(keyDev.devid);
        keyDev.minor = MINOR(keyDev.devid);
    }
    printk("keydev major=%d,minor=%d\r\n", keyDev.major,
        keyDev.minor);

        keyDev.cdev.owner = THIS_MODULE;
    cdev_init(&keyDev.cdev, &key_fops);

    ret = cdev_add(&keyDev.cdev, keyDev.devid, KEY_CNT);
    if (ret < 0)
    {
        pr_err("cdev_add failed, ret=%d\r\n", ret);
        goto del_unregister;
    }
    // 创建类
    keyDev.class = class_create(THIS_MODULE, KEY_NAME);
    if (IS_ERR(keyDev.class))
    {
        pr_err("class_create failed, ret=%d\r\n", ret);
        goto del_cdev;
    }
    // 创建设备
    keyDev.device = device_create(keyDev.class, NULL,
                keyDev.devid, NULL, KEY_NAME);
    if (IS_ERR(keyDev.device))
    {
        pr_err("device_create failed, ret=%d\r\n", ret);
        goto del_class;
    }
    return 0;

del_class:
    device_destroy(keyDev.class,keyDev.devid);
del_cdev:
    cdev_del(&keyDev.cdev);
del_unregister:
    unregister_chrdev_region(keyDev.devid, KEY_CNT);
    return -EIO;
}

static void __exit mykey_exit(void)
{
    // 销毁设备
    device_destroy(keyDev.class, keyDev.devid);
    // 销毁类
    class_destroy(keyDev.class);
    // 删除cdev
    cdev_del(&keyDev.cdev);
    // 注销字符设备
    unregister_chrdev_region(keyDev.devid, KEY_CNT);
    printk("key driver exit\r\n");
}

module_init(mykey_init);
module_exit(mykey_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("alientek");
MODULE_INFO(intree, "jack yang");
